﻿namespace Creek.UI.EFML.Base.CSS
{
    using System;
    using System.Linq;

    public class TextParser
    {
        #region Static Fields

        public static char NullChar = (char)0;

        #endregion

        #region Fields

        private int _pos;

        private string _text;

        #endregion

        #region Constructors and Destructors

        public TextParser()
        {
            this.Reset(null);
        }

        public TextParser(string text)
        {
            this.Reset(text);
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// 	Indicates if the current position is at the end of the current document
        /// </summary>
        public bool EndOfText
        {
            get
            {
                return (this._pos >= this._text.Length);
            }
        }

        public int Position
        {
            get
            {
                return this._pos;
            }
        }

        public int Remaining
        {
            get
            {
                return this._text.Length - this._pos;
            }
        }

        public string Text
        {
            get
            {
                return this._text;
            }
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// 	Extracts a substring from the specified position to the end of the text
        /// </summary>
        /// <param name="start"> </param>
        /// <returns> </returns>
        public string Extract(int start)
        {
            return this.Extract(start, this._text.Length);
        }

        /// <summary>
        /// 	Extracts a substring from the specified range of the current text
        /// </summary>
        /// <param name="start"> </param>
        /// <param name="end"> </param>
        /// <returns> </returns>
        public string Extract(int start, int end)
        {
            return this._text.Substring(start, end - start);
        }

        /// <summary>
        /// 	Moves the current position ahead one character
        /// </summary>
        public void MoveAhead()
        {
            this.MoveAhead(1);
        }

        /// <summary>
        /// 	Moves the current position ahead the specified number of characters
        /// </summary>
        /// <param name="ahead"> The number of characters to move ahead </param>
        public void MoveAhead(int ahead)
        {
            this._pos = Math.Min(this._pos + ahead, this._text.Length);
        }

        /// <summary>
        /// 	Moves to the next occurrence of any character that is not one
        /// 	of the specified characters
        /// </summary>
        /// <param name="chars"> Array of characters to move past </param>
        public void MovePast(char[] chars)
        {
            while (this.IsInArray(this.Peek(), chars))
            {
                this.MoveAhead();
            }
        }

        /// <summary>
        /// 	Moves the current position to the next character that is not whitespace
        /// </summary>
        public void MovePastWhitespace()
        {
            while (Char.IsWhiteSpace(this.Peek()))
            {
                this.MoveAhead();
            }
        }

        /// <summary>
        /// 	Moves to the next occurrence of the specified string
        /// </summary>
        /// <param name="s"> String to find </param>
        /// <param name="ignoreCase"> Indicates if case-insensitive comparisons are used </param>
        public void MoveTo(string s, bool ignoreCase = false)
        {
            this._pos = this._text.IndexOf(
                s, this._pos, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
            if (this._pos < 0)
            {
                this._pos = this._text.Length;
            }
        }

        /// <summary>
        /// 	Moves to the next occurrence of the specified character
        /// </summary>
        /// <param name="c"> Character to find </param>
        public void MoveTo(char c)
        {
            this._pos = this._text.IndexOf(c, this._pos);
            if (this._pos < 0)
            {
                this._pos = this._text.Length;
            }
        }

        /// <summary>
        /// 	Moves to the next occurrence of any one of the specified
        /// 	characters
        /// </summary>
        /// <param name="chars"> Array of characters to find </param>
        public void MoveTo(char[] chars)
        {
            this._pos = this._text.IndexOfAny(chars, this._pos);
            if (this._pos < 0)
            {
                this._pos = this._text.Length;
            }
        }

        /// <summary>
        /// 	Moves the current position to the first character that is part of a newline
        /// </summary>
        public void MoveToEndOfLine()
        {
            char c = this.Peek();
            while (c != '\r' && c != '\n' && !this.EndOfText)
            {
                this.MoveAhead();
                c = this.Peek();
            }
        }

        /// <summary>
        /// 	Returns the character at the current position, or a null character if we're
        /// 	at the end of the document
        /// </summary>
        /// <returns> The character at the current position </returns>
        public char Peek()
        {
            return this.Peek(0);
        }

        /// <summary>
        /// 	Returns the character at the specified number of characters beyond the current
        /// 	position, or a null character if the specified position is at the end of the
        /// 	document
        /// </summary>
        /// <param name="ahead"> The number of characters beyond the current position </param>
        /// <returns> The character at the specified position </returns>
        public char Peek(int ahead)
        {
            int pos = (this._pos + ahead);
            if (pos < this._text.Length)
            {
                return this._text[pos];
            }
            return NullChar;
        }

        /// <summary>
        /// 	Resets the current position to the start of the current document
        /// </summary>
        public void Reset()
        {
            this._pos = 0;
        }

        /// <summary>
        /// 	Sets the current document and resets the current position to the start of it
        /// </summary>
        public void Reset(string text)
        {
            this._text = text ?? String.Empty;
            this._pos = 0;
        }

        #endregion

        #region Methods

        /// <summary>
        /// 	Determines if the specified character exists in the specified
        /// 	character array.
        /// </summary>
        /// <param name="c"> Character to find </param>
        /// <param name="chars"> Character array to search </param>
        /// <returns> </returns>
        protected bool IsInArray(char c, char[] chars)
        {
            return chars.Any(ch => c == ch);
        }

        #endregion
    }
}